/*******************************************************************************
 * Copyright (c) 2000, 2019 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Remy Chi Jian Suen <remy.suen@gmail.com> - Bug 214424 IOConsole(String, String, ImageDescriptor, String, boolean) constructor is missing api javadoc
 *******************************************************************************/
package cn.bbstone.e4.ui.log.views;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.eclipse.e4.ui.di.UISynchronize;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ui.WorkbenchEncoding;
import org.eclipse.ui.console.IConsoleDocumentPartitioner;
import org.eclipse.ui.console.IOConsoleInputStream;
import org.eclipse.ui.console.TextConsole;

import cn.bbstone.e4.ui.log.logger.ILogConsole;

/**
 * A console that displays text from I/O streams. An I/O console can have multiple
 * output streams connected to it and provides one input stream connected to the
 * keyboard.
 * <p>
 * Clients may instantiate and subclass this class.
 * </p>
 * @since 3.1
 * @see org.eclipse.ui.console.IOConsole
 * 
 * NOTICE: this source file is copy from {@link org.eclipse.ui.console.IOConsole}, 
 * 	and adapted to match E4 application model.
 * 参考IOConsole实现，将可用的IOConsole代码搬过来，NOTICE：记得将搬过来的代码标明出处，版权等信息。
 * 
 * @author bbstone
 *
 */
public class LogsIOConsole extends TextConsole implements ILogConsole {
	

	/**
	 * Constructs a console with the given name, type, image, and lifecycle, with the
	 * workbench's default encoding.
	 *
	 * @param name name to display for this console
	 * @param consoleType console type identifier or <code>null</code>
	 * @param imageDescriptor image to display for this console or <code>null</code>
	 * @param autoLifecycle whether lifecycle methods should be called automatically
	 *  when this console is added/removed from the console manager
	 */
	public LogsIOConsole(UISynchronize sync, String name, String consoleType, ImageDescriptor imageDescriptor, boolean autoLifecycle) {
		this(sync, name, consoleType, imageDescriptor, (String)null, autoLifecycle);
	}

	/**
	 * Constructs a console with the given name, type, image, encoding and lifecycle.
	 *
	 * @param name name to display for this console
	 * @param consoleType console type identifier or <code>null</code>
	 * @param imageDescriptor image to display for this console or <code>null</code>
	 * @param encoding the encoding that should be used to render the text, or <code>null</code>
	 * 	if the system default encoding should be used
	 * @param autoLifecycle whether lifecycle methods should be called automatically
	 *  when this console is added/removed from the console manager
	 */
	public LogsIOConsole(UISynchronize sync, String name, String consoleType, ImageDescriptor imageDescriptor, String encoding, boolean autoLifecycle) {
		this(sync, name, consoleType, imageDescriptor,
				encoding == null
				? Charset.forName(WorkbenchEncoding.getWorkbenchDefaultEncoding())
						: Charset.forName(encoding),
						autoLifecycle);
	}

	/**
	 * Constructs a console with the given name, type, image, encoding and
	 * lifecycle.
	 *
	 * @param name name to display for this console
	 * @param consoleType console type identifier or <code>null</code>
	 * @param imageDescriptor image to display for this console or
	 *            <code>null</code>
	 * @param charset the encoding that should be used to render the text, must
	 *            not be <code>null</code>
	 * @param autoLifecycle whether lifecycle methods should be called
	 *            automatically when this console is added/removed from the
	 *            console manager
	 * @since 3.7
	 */
	public LogsIOConsole(UISynchronize sync, String name, String consoleType, ImageDescriptor imageDescriptor, Charset charset, boolean autoLifecycle) {
		super(name, consoleType, imageDescriptor, autoLifecycle);
		this.charset = charset;
		synchronized (openStreams) {
			inputStream = new LogsIOConsoleInputStream(this);
			openStreams.add(inputStream);
		}
		partitioner = new LogsIOConsolePartitioner(sync, this);
		final IDocument document = getDocument();
		if (document != null) {
			partitioner.connect(document);
			document.setDocumentPartitioner(partitioner);
		}
	}

	/**
	 * Constructs a console with the given name, type, and image with the workbench's
	 * default encoding. Lifecycle methods will be called when this console is
	 * added/removed from the console manager.
	 *
	 * @param name name to display for this console
	 * @param consoleType console type identifier or <code>null</code>
	 * @param imageDescriptor image to display for this console or <code>null</code>
	 */
	public LogsIOConsole(UISynchronize sync, String name, String consoleType, ImageDescriptor imageDescriptor) {
		this(sync, name, consoleType, imageDescriptor, true);
	}

	/**
	 * Constructs a console with the given name and image. Lifecycle methods
	 * will be called when this console is added/removed from the console manager.
	 * This console will have an unspecified (<code>null</code>) type.
	 *
	 * @param name name to display for this console
	 * @param imageDescriptor image to display for this console or <code>null</code>
	 */
	public LogsIOConsole(UISynchronize sync, String name, ImageDescriptor imageDescriptor) {
		this(sync, name, null, imageDescriptor);
	}

	/**
	 * The document partitioner
	 */
	private final LogsIOConsolePartitioner partitioner;

	/**
	 * The stream from which user input may be read
	 */
	private InputStream inputStream;

	/**
	 * A collection of open streams connected to this console.
	 */
	private final List<Closeable> openStreams = Collections.synchronizedList(new ArrayList<>());

	private final Charset charset;

	@Override
	public void dispose() {
		// Get lock on ourself before closing. Closing streams check for console finish.
		// Finish check need lock on (this) console. Since closing also lock on stream
		// this can deadlock with other threads (double) closing streams at same time
		// but already own a lock on console. (bug 551902)
		// Long story short. Before closing IOConsole...Stream get lock from associated
		// console to prevent deadlocks.
		synchronized (this) {
			// make a copy of the open streams and close them all
			// a copy is needed as close the streams results in a callback that
			// removes the streams from the openStreams collection (bug 152794)
			List<Closeable> list = new ArrayList<>(openStreams);
			for (Closeable closable : list) {
				try {
					closable.close();
				} catch (IOException e) {
					// e.printStackTrace();
				}
			}
			inputStream = null;
		}

		final IDocument document = partitioner.getDocument();
		if (document != null)
			document.setDocumentPartitioner(null);
		partitioner.disconnect();

		super.dispose();
	}

	public LogsIOConsoleOutputStream newOutputStream() {
		LogsIOConsoleOutputStream outputStream = new LogsIOConsoleOutputStream(this, StandardCharsets.UTF_8);
		addOpenStream((Closeable) outputStream);
		return outputStream;
	}

	/**
	 * Registers a stream that will be managed by this console.
	 *
	 * @param stream The stream which will be closed on {@link #dispose()}.
	 */
	void addOpenStream(Closeable stream) {
		synchronized (openStreams) {
			openStreams.add(stream);
		}
	}

	/**
	 * Returns the input stream connected to the keyboard.
	 * <p>
	 * Note: It returns the stream connected to keyboard. There is no guarantee to
	 * get the stream last set with {@link #setInputStream(InputStream)}. The return
	 * value might be <code>null</code> if the current input stream is not connected
	 * to the keyboard.
	 * </p>
	 *
	 * @return the input stream connected to the keyboard.
	 */
	public LogsIOConsoleInputStream getInputStream() {
		if (inputStream instanceof IOConsoleInputStream) {
			return (LogsIOConsoleInputStream) inputStream;
		}
		return null;
	}
	
	/**
	 * Returns the Charset for this console.
	 *
	 * @return the Charset for this console
	 * @since 3.7
	 */
	public Charset getCharset() {
		return this.charset;
	}
	
	/**
	 * Notification that an output stream connected to this console has been closed.
	 *
	 * @param stream stream that closed
	 */
	void streamClosed(LogsIOConsoleOutputStream stream) {
		synchronized (openStreams) {
			openStreams.remove(stream);
			checkFinished();
		}
	}

	/**
	 * Notification that the input stream connected to this console has been closed.
	 *
	 * @param stream stream that closed
	 */
	void streamClosed(LogsIOConsoleInputStream stream) {
		synchronized (openStreams) {
			openStreams.remove(stream);
			checkFinished();
		}
	}
	
	/**
	 * Check if all streams connected to this console are closed. If so,
	 * notify the partitioner that this console is finished.
	 */
	private void checkFinished() {
		if (openStreams.isEmpty()) {
			partitioner.streamsClosed();
		}
	}
	

	@Override
	protected IConsoleDocumentPartitioner getPartitioner() {
		return partitioner;
	}

}
